﻿//
// Copyright (c) 2011 David Díaz & Fran Pedreira
//
// This file is part of PlanSim.
//
// PlanSim is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PlanSim is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with PlanSim. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using PlanSim.Core;

namespace PlanSim.Interfaz
{
    public partial class FormSimulacion : Form
    {
		enum SubitemsProceso : int
		{
			Inicio = 1,
			Duracion,
			TiempoRespuesta,
			TiempoRetorno,
			TiempoEspera
		}

        const int MaxElementosEnCola = 10;
		const int FrecuenciaPorDefecto = 10;
		const string NombreColaES = "_es";

		/// <summary>
		/// Configuración de la simulación.
		/// </summary>
		Configuracion configuracion;

		/// <summary>
		/// Representa la simulación en curso.
		/// </summary>
		Simulacion simulacion;

		/// <summary>
		/// Constructor del formulario.
		/// </summary>
        public FormSimulacion(Configuracion configuracion)
        {
			// Se guarda la configuración de la simulación para usarla más adelante.
            if (configuracion == null)
                throw new Exception("Configuracion == null");

			this.configuracion = configuracion;

            InitializeComponent();
        }

		/// <summary>
		/// Inicializamos el chiringuito cuando sabemos que ya está todo cargado.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

            cronograma.ColorFondo = Color.White;
            cronograma.ColorMarcas = Color.Black;
            cronograma.ColorCursor = Color.Black;

			cmbFrecuencia.SelectedIndex = 1;
			EstadoControles(true);

            this.Text = "Simulación [" + configuracion.Nombre + "]";

            InicializarDatosSimulacion();
		}

		protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
		{
			base.OnClosing(e);

			if (simulacion!=null)
				simulacion.DetenerSimulacion();
		}

        private void InicializarDatosSimulacion()
        {
            if (configuracion != null)
            {
                ////////////////
                // GRID DE COLAS
                ////////////////

                // Crea una columna por cola en el grid.
                grdColas.Columns.Clear();

				// Cola única de E/S
				grdColas.Columns.Add(NombreColaES, "ES (FCFS)");

				// Colas de preparados.
                foreach (Configuracion.Cola c in configuracion.Colas)
                    grdColas.Columns.Add(c.Nombre, c.Nombre + " (" + c.Prioridad + "-" + c.Tipo + ")");

                // Crea espacio para "ElementosEnCola" procesos encolados.
				for (int i = 0; i < MaxElementosEnCola; ++i)
					grdColas.Rows.Add(new DataGridViewRow());

                ////////////////
                // LISTA DE PROCESOS
                ////////////////

                // Añade un item por proceso y un subitem por cada atributo
                lstProcesos.Items.Clear();
                foreach (Configuracion.Proceso p in configuracion.Procesos)
                {
                    ListViewItem i = lstProcesos.Items.Add(p.Nombre, p.Nombre, null);
                    i.SubItems.Add(p.Entrada.ToString());		// Inicio
                    i.SubItems.Add(p.Duracion.ToString());		// Duración
					i.SubItems.Add(string.Empty);				// Tiempo de respuesta
					i.SubItems.Add(string.Empty);				// Tiempo de retorno
					i.SubItems.Add(string.Empty);				// Tiempo de espera
                }

                ////////////////
                // LISTA DE DISPOSITIVOS
                ////////////////

                // Añade un item por dispositivo y un subitem para indicar qué proceso está usando el dispositivo.
				lstDispositivos.Items.Clear();
                foreach (Configuracion.Dispositivo d in configuracion.Dispositivos)
                {
                    ListViewItem i = lstDispositivos.Items.Add(d.Nombre, d.Nombre, null);
                    i.SubItems.Add(string.Empty);
                }

				////////////////
				// FUERZA EL REFRESCO DEL CRONOGRAMA.
				////////////////
				cronograma.PosicionCursor = 0;
				cronograma.Simulacion = null;
			}
        }

		/// <summary>
		/// Evento que se recibe cuando finaliza un ciclo de simulación. Aprovechamos para
		/// actualizar los datos del formulario.
		/// </summary>
		/// <param name="sender">Objeto que lanza el evento.</param>
		/// <param name="e">Parámetros del evento. Contiene el Ciclo de simulación que acaba de terminar.</param>
		void Planificador_FinalizaCiclo(object sender, EventArgs e)
		{
			CicloEventArgs args = (CicloEventArgs)e;
			RefrescarInformacion(args.Ciclo);
		}

		/// <summary>
		/// Evento que se recibe cuando finaliza la simulación.
		/// </summary>
		/// <param name="sender">Objeto que lanza el evento.</param>
		/// <param name="e">Parámetros del evento. No usado.</param>
		void Planificador_FinalizaSimulacion(object sender, EventArgs e)
		{
			// Vuelve a habilitar los controles
			EstadoControles(true);

			// Muestra los tiempos de simulación obtenidos.
			RefrescarTiempos();
		}

		void EstadoControles(bool estado)
		{
			// Si la llamada viene de otro hilo (por provenir de un evento) reentramos
			// por el hilo que creó el componente visual. Si no... casque.
			if (InvokeRequired)
			{
				BeginInvoke(new MethodInvoker(delegate() { EstadoControles(estado); }));
				return;
			}

			btnGuardar.Enabled = estado;
			btnEditar.Enabled = estado;
			btnPlay.Enabled = estado;
			btnPausa.Enabled = !estado;
			cmbFrecuencia.Enabled = estado;
			hScrollBar1.Enabled = estado;
		}
	
		/// <summary>
		/// Actualiza la información en pantalla: gráfico, etiquetas, etc.
		/// </summary>
		/// <param name="ciclo">Último ciclo de simulación ejecutado.</param>
		private void RefrescarInformacion(int ciclo)
		{
			if (simulacion == null)
				return;

			// Si la llamada viene de otro hilo (por provenir de un evento) reentramos
			// por el hilo que creó el componente visual. Si no... casque.
			if (InvokeRequired)
			{
				BeginInvoke(new MethodInvoker(delegate() { RefrescarInformacion(ciclo); }));
				return;
			}

            // Muestra el ciclo actual de simulación
			lblCiclo.Text = ciclo.ToString();

            // Resalta el proceso que está asignado a la CPU
            foreach (ListViewItem item in lstProcesos.Items)
            {
				if (simulacion.Planificador.ProcesoEnCPU != null && item.Text == simulacion.Planificador.ProcesoEnCPU.Nombre)
				{
					item.BackColor = simulacion.Planificador.ProcesoEnCPU.Color;
					item.ForeColor = Color.White;
				}
				else
				{
					item.ForeColor = Color.Black;
					item.BackColor = Color.White;
				}
            }

			// Muestra los procesos que están en cola de E/S
			int i;
			for (i = 0; i < Math.Min(simulacion.Planificador.ColasEntradaSalida[0].Elementos(), MaxElementosEnCola); ++i)
				grdColas.Rows[i].Cells[grdColas.Columns[NombreColaES].Index].Value = simulacion.Planificador.ColasEntradaSalida[0].Elemento(i).Nombre;

			for (; i < MaxElementosEnCola; ++i)
			    grdColas.Rows[i].Cells[grdColas.Columns[NombreColaES].Index].Value = string.Empty;

			// Muestra los procesos que hay en cada cola
            for (int c=0; c<simulacion.Planificador.ColasPreparados.Count; ++c)
            {
                ICola cola = simulacion.Planificador.ColasPreparados[c];
                DataGridViewColumn col = grdColas.Columns[cola.Nombre];

                for (i = 0; i < Math.Min(cola.Elementos(), MaxElementosEnCola); ++i)
                    grdColas.Rows[i].Cells[col.Index].Value = cola.Elemento(i).Nombre;

                for (; i < MaxElementosEnCola; ++i)
                    grdColas.Rows[i].Cells[col.Index].Value = string.Empty;
            }

            if (lstDispositivos.Items.Count>0)
                if (simulacion.Planificador.ProcesoEnEntradaSalida != null)
                    lstDispositivos.Items[0].SubItems[1].Text = simulacion.Planificador.ProcesoEnEntradaSalida.Nombre;
                else
                    lstDispositivos.Items[0].SubItems[1].Text = string.Empty;

            // Actualiza el cronograma
			cronograma.Actualizar();
			cronograma.PosicionCursor = ciclo;
			hScrollBar1.Value = hScrollBar1.Maximum = ciclo;
		}

		private void RefrescarTiempos()
		{
			if (InvokeRequired)
			{
				BeginInvoke(new MethodInvoker(delegate() { RefrescarTiempos(); }));
				return;
			}

			foreach (ListViewItem item in lstProcesos.Items)
			{
				foreach (Proceso p in simulacion.Procesos)
				{
					if (item.Text == p.Nombre)
					{
						item.SubItems[(int)SubitemsProceso.TiempoRespuesta].Text = (p.Inicio - p.Entrada).ToString();
						item.SubItems[(int)SubitemsProceso.TiempoRetorno].Text = (p.Fin - p.Inicio + 1).ToString();
						item.SubItems[(int)SubitemsProceso.TiempoEspera].Text = "CPU:" + p.CiclosEsperaCPU.ToString() + " + ES:" + p.CiclosEsperaES.ToString();
					}
				}
			}
		}

		private void BorrarTiempos()
		{
			if (InvokeRequired)
			{
				BeginInvoke(new MethodInvoker(delegate() { BorrarTiempos(); }));
				return;
			}

			foreach (ListViewItem item in lstProcesos.Items)
			{
				item.SubItems[(int)SubitemsProceso.TiempoRespuesta].Text = string.Empty;
				item.SubItems[(int)SubitemsProceso.TiempoRetorno].Text = string.Empty;
				item.SubItems[(int)SubitemsProceso.TiempoEspera].Text = string.Empty;
			}
		}

        private void hScrollBar1_ValueChanged(object sender, EventArgs e)
        {
            cronograma.PosicionCursor = hScrollBar1.Value;
        }

		private void btnGuardar_Click(object sender, EventArgs e)
		{
			if (configuracion != null)
			{
				SaveFileDialog dialog = new SaveFileDialog();

				dialog.AddExtension = true;
				dialog.DefaultExt = "configuracion.xml";
				dialog.Filter = "Configuración|*.configuracion.xml";
				dialog.CheckPathExists = true;
				dialog.OverwritePrompt = true;
				dialog.Title = "Guardar configuración";
                dialog.InitialDirectory = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "Simulaciones");

				if (dialog.ShowDialog() == DialogResult.OK)
					configuracion.GuardarEnFichero(dialog.FileName);
			}
		}

        private void toolStripButton1_Click(object sender, EventArgs e)
        {
			// Iniciar nueva simulación
			if (simulacion == null || (simulacion != null && simulacion.Planificador.Estado == Planificador.EstadoPlanificador.Detenido))
			{
				if (simulacion != null)
				{
					simulacion.Planificador.FinalizaCiclo -= Planificador_FinalizaCiclo;
					simulacion.Planificador.FinalizaSimulacion -= Planificador_FinalizaSimulacion;
				}

				simulacion = new Simulacion(configuracion);
				simulacion.Planificador.FinalizaCiclo += Planificador_FinalizaCiclo;
				simulacion.Planificador.FinalizaSimulacion += Planificador_FinalizaSimulacion;

				BorrarTiempos();

				cronograma.Simulacion = simulacion;
			}

			// Deshabilita los controles para que el usuario no enmerde durante la simulación.
			EstadoControles(false);

			int frecuencia;
			if (!int.TryParse(cmbFrecuencia.Text, out frecuencia))
				frecuencia = FrecuenciaPorDefecto;

			// Inicia o reanuda de la pausa la simulación.
            simulacion.IniciarSimulacion(frecuencia);
        }

        private void btnEditar_Click(object sender, EventArgs e)
        {
            if (configuracion == null)
                return;

            FormWizard wizard = new FormWizard(this.configuracion);
			if (wizard.ShowDialog(this) == DialogResult.OK)
				InicializarDatosSimulacion();
        }

		private void btnPausa_Click(object sender, EventArgs e)
		{
			simulacion.PausarSimulacion();

			btnPlay.Enabled = true;
			cmbFrecuencia.Enabled = true;
			btnPausa.Enabled = false;
		}
    }
}
